//
// Created by 郝羽 on 2018/3/22.
//
#include <cstdio>
#include <glib.h>
#include <gio/gio.h>
#include <netinet/in.h>
#include <list>

GMainContext* g_mc;
GSocket* g_sock;
GError* g_err;
guint g_read;
guint g_write;
class Packet {
public:
    sockaddr_in remote;
    unsigned char* buf;
    int bufLen;
};
std::list<Packet*> g_packets;

int bytes_in = 0;
int bytes_out = 0;

gboolean handleWrite(GIOChannel* ch, GIOCondition cond, gpointer) {
    while (g_packets.size()) {
        auto pkt = g_packets.front();
        int res = sendto(g_socket_get_fd(g_sock), pkt->buf, pkt->bufLen, 0, (sockaddr*)&pkt->remote, sizeof(pkt->remote));
        if (res <= 0) {
            fprintf(stderr, "could not write: %d\n", res);
            break;
        }
        bytes_in += res;
        //fprintf(stderr, "read %d\n", res);
        g_packets.pop_front();
        delete[] pkt->buf;
        delete pkt;
    }
    return FALSE;
}

gboolean handleRead(GIOChannel* ch, GIOCondition cond, gpointer) {
    sockaddr_in sa;
    socklen_t sal = sizeof(sa);
    while (true) {
        unsigned char* buf = new unsigned char[1500];
        int res = recvfrom(g_socket_get_fd(g_sock), buf, 1500, 0, (sockaddr*)&sa, &sal);
        if (res <= 0) {
            free(buf);
            break;
        }
        bytes_out += res;
        auto pkt = new Packet();
        pkt->remote = sa;
        pkt->buf = buf;
        pkt->bufLen = res;
        g_packets.emplace_back(pkt);
        g_write = g_io_add_watch(ch, G_IO_OUT, handleWrite, nullptr);
    }
    return TRUE;
}

gboolean handleTimeout(gpointer user_data){
    fprintf(stderr, "recv %8d send %8d\n", bytes_in, bytes_out);
    bytes_in = 0;
    bytes_out = 0;
    return TRUE;
}


int main() {
    g_mc = g_main_context_new();
    if (!g_mc) {
        fprintf(stderr, "could not create main context");
        return -1;
    }
    g_sock = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_UDP, &g_err);
    if (!g_sock) {
        fprintf(stderr, "could not create socket: %s", g_err->message);
        return -1;
    }
    GSocketAddress* la = G_SOCKET_ADDRESS(g_inet_socket_address_new(g_inet_address_new_any(G_SOCKET_FAMILY_IPV4), 30000));
    if (g_socket_bind(g_sock, la, TRUE, &g_err) == FALSE) {
        fprintf(stderr, "could not bind: %s", g_err->message);
        return -1;
    }

    GIOChannel* ch = g_io_channel_unix_new(g_socket_get_fd(g_sock));
    g_read = g_io_add_watch(ch, G_IO_IN, handleRead, nullptr);
    g_io_channel_unref(ch);

    auto t = g_timeout_source_new(1000);
    g_source_set_callback(t, handleTimeout, nullptr, nullptr);
    g_source_attach(t, nullptr);
    GMainLoop * gloop = g_main_loop_new(nullptr, FALSE);
    g_main_loop_run(gloop);
}

